<!DOCTYPE html>
<html lang="en">
    <head>
        <title>Reconstruction Viewer</title>
        <meta charset="utf-8">
        <style>
            body {
                margin: 0px;
            }
            #load_button {
               position: absolute;
               top: 50%;
               left: 50%;
               width: 300px;
               height: 120px;
               margin-top: -60px; /* Half the height */
               margin-left: -150px; /* Half the width */
            }

            #loading {
               position: absolute;
               top: 50%;
               left: 50%;
               width: 245px;
               height: 148px;
               margin-top: -74px; /* Half the height */
               margin-left: -122px; /* Half the width */
               display: none;
            }

            #info {
                position: absolute;
                top: 0px;
                left: 10px;
                background-color: #000;
                visibility: hidden;
            }
            #image {
                width: 160px;
                height: 120px;
            }
            #text {
                text-align: center;
                font-family: sans-serif;
                font-size: 10px;
                color: #FFF;
            }
            #reconstruction {
                text-align: center;
                font-family: sans-serif;
                font-size: 10px;
                color: #FFF;
            }
            #navigation {
                position: absolute;
                top: 0px;
                left: 10px;
                display: none;
                font-family: sans-serif;
                font-size: 30px;
            }
        </style>
    </head>
    <body>
        <div id="ThreeJS" target="_blank"></div>

        <input id='load_button' type="file" value="open reconstruction" />

        <div id="loading">
            <img src="images/preloader.gif">
        </div>

        <div id="info">
            <img id="image">
            <div id="text"></div>
            <div id="reconstruction"></div>
        </div>
        <div id="navigation">
            <table>
                <tr>
                    <td id="nav-turn-left">
                        <button onclick="walkOneStep('TURN_LEFT')">
                            &#8624;
                        </button>
                    </td>
                    <td id="nav-forward">
                        <button onclick="walkOneStep('STEP_FORWARD')">
                            &#8593;
                        </button>
                    </td>
                    <td id="nav-turn-right">
                        <button onclick="walkOneStep('TURN_RIGHT')">
                            &#8625;
                        </button>
                    </td>
                </tr>
                <tr>
                    <td id="nav-left">
                        <button onclick="walkOneStep('STEP_LEFT')">
                            &#8592;
                        </button>
                    </td>
                    <td id="nav-u-turn">
                        <button onclick="walkOneStep('TURN_U')">
                            &#8630;
                        </button>
                    </td>
                    <td id="nav-right">
                        <button onclick="walkOneStep('STEP_RIGHT')">
                            &#8594;
                        </button>
                    </td>
                </tr>
                <tr>
                    <td>
                    </td>
                    <td id="nav-backward">
                        <button onclick="walkOneStep('STEP_BACKWARD')">
                            &#8595;
                        </button>
                    </td>
                    <td>
                        <button onclick="setMovingMode('orbit')">
                            fly
                        </button>
                    </td>
                </tr>
            </table>
        </div>

        <script src="js/jquery.js"></script>
        <script src="js/three.js"></script>
        <script src="js/OrbitControls.js"></script>
        <script src="js/TrackballControls.js"></script>
        <script src="js/dat.gui.js"></script>

        <!-- Shaders -->
        <script type="x-shader/x-vertex" id="vertexshader">

            // switch on high precision floats
            #ifdef GL_ES
            precision highp float;
            #endif

            varying vec4 vRstq;
            uniform mat4 projectorMat;

            void main()
            {
                vRstq = projectorMat * vec4(position, 1.0);
                gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0);
            }

        </script>

        <script type="x-shader/x-fragment" id="fragmentshader">

            #ifdef GL_ES
            precision highp float;
            #endif

            varying vec4 vRstq;
            uniform sampler2D projectorTex;
            uniform float opacity;
            uniform float focal;
            uniform float k1;
            uniform float k2;
            uniform float scale_x;
            uniform float scale_y;

            void main()
            {
                float x = vRstq.x / vRstq.z;
                float y = vRstq.y / vRstq.z;
                float r2 = x * x + y * y;
                float d = 1.0 + k1 * r2 + k2 * r2 * r2;
                float u = scale_x * focal * d * x + 0.5;
                float v = - scale_y * focal * d * y + 0.5;

                vec4 baseColor = texture2D(projectorTex, vec2(u, v));
                baseColor.a = opacity;
                gl_FragColor = baseColor;
            }

        </script>

        <script type="x-shader/x-fragment" id="fragmentshader_fisheye">

            #ifdef GL_ES
            precision highp float;
            #endif

            varying vec4 vRstq;
            uniform sampler2D projectorTex;
            uniform float opacity;
            uniform float focal;
            uniform float k1;
            uniform float k2;
            uniform float scale_x;
            uniform float scale_y;

            void main()
            {
                float x = vRstq.x;
                float y = vRstq.y;
                float z = vRstq.z;

                float a = x / z;
                float b = y / z;

                float r = sqrt(a * a + b * b);
                float theta = atan(r);
                float theta2 = theta * theta;
                float theta_d = theta * (1.0 + theta2 * (k1 + theta2 * k2));
                float inv_r = r > 1e-8 ? 1.0 / r : 1.0;
                float cdist = r > 1e-8 ? theta_d * inv_r : 1.0;
                float x_p = cdist * a;
                float y_p = cdist * b;

                float u = scale_x * focal * x_p + 0.5;
                float v = -scale_y * focal * y_p + 0.5;

                vec4 baseColor = texture2D(projectorTex, vec2(u, v));
                baseColor.a = opacity;
                gl_FragColor = baseColor;
            }

        </script>

        <script type="x-shader/x-fragment" id="fragmentshader_fisheye_opencv">

            #ifdef GL_ES
            precision highp float;
            #endif

            varying vec4 vRstq;
            uniform sampler2D projectorTex;
            uniform float opacity;
            uniform float focal_x;
            uniform float focal_y;
            uniform float c_x;
            uniform float c_y;
            uniform float k1;
            uniform float k2;
            uniform float k3;
            uniform float k4;
            uniform float scale_x;
            uniform float scale_y;

            void main()
            {
                float x = vRstq.x;
                float y = vRstq.y;
                float z = vRstq.z;

                float a = x / z;
                float b = y / z;

                float r = sqrt(a * a + b * b);
                float theta = atan(r);
                float theta2 = theta * theta;
                float theta_d = theta * (1.0 + theta2 * (k1 + theta2 * (k2 + theta2 * (k3 + theta2 * k4))));
                float inv_r = r > 1e-8 ? 1.0 / r : 1.0;
                float cdist = r > 1e-8 ? theta_d * inv_r : 1.0;
                float x_p = cdist * a;
                float y_p = cdist * b;

                float u = scale_x * focal_x * x_p + c_x + 0.5;
                float v = -scale_y * focal_y * y_p + c_y + 0.5;

                vec4 baseColor = texture2D(projectorTex, vec2(u, v));
                baseColor.a = opacity;
                gl_FragColor = baseColor;
            }

        </script>

        <script type="x-shader/x-fragment" id="fragmentshader_equirectangular">

            #ifdef GL_ES
            precision highp float;
            #endif

            #define tau 6.28318530718

            varying vec4 vRstq;
            uniform sampler2D projectorTex;
            uniform float opacity;

            void main()
            {
                vec3 b = normalize(vRstq.xyz);
                float lat = -asin(b.y);
                float lon = atan(b.x, b.z);
                float x = lon / tau + 0.5;
                float y = lat / tau * 2.0 + 0.5;
                vec4 baseColor = texture2D(projectorTex, vec2(x, y));
                baseColor.a = opacity;
                gl_FragColor = baseColor;
            }

        </script>

        <script>
            var urlParams;
            var f1;
            var container, camera, controls, scene, renderer;
            var mouse = new THREE.Vector2();
            var hoverCamera, raycaster, parentTransform;
            var selectedCamera;
            var imagePlane, imagePlaneCamera;
            var imagePlaneOld, imagePlaneCameraOld;
            var scene_group, grid_group;
            var pointCloudMaterial;
            var reconstructions;
            var reconstruction_visibles = [];
            var reconstruction_groups = [];
            var point_clouds = [];
            var camera_lines = [];
            var gps_lines = [];
            var imagePlanes = [];
            var imagePlaneCameras = [];
            var imageMaterials = [];
            var num_preview_plane = 5;
            var moveSpeed = 0.2;
            var turnSpeed = 0.1;
            var previousShot = undefined;
            var validMoves;
            var movingMode = 'orbit';
            var savedOptions = {
                cameraSize: 0,
                pointSize: 0,
                showThumbnail: false,
                showImagePlane: true,
                drawGrid: false,
                drawGPS: false
            };

            var options = {
                cameraSize: 0.9,
                pointSize: 0.7,
                imagePlaneSize: 50,
                showThumbnail: true,
                showImagePlane: false,
                drawGrid: true,
                drawGPS: false,
                animationSpeed: 0.1,
                imagePlaneOpacity: 1,
                cameraColor: new THREE.Color(0xFFFFFF),
                hoverCameraColor: new THREE.Color(0xFF8888),
                selectedCameraColor: new THREE.Color(0xFFFF88),
                reconstruction_visibles: {},
                resolution: 'original',
                allNone: function () {
                    var someone = false;
                    for (var r = 0; r < reconstructions.length; ++r) {
                        if (options.reconstruction_visibles[r]) {
                            someone = true;
                            break;
                        }
                    }
                    for (var r = 0; r < reconstructions.length; ++r) {
                        options.reconstruction_visibles[r] = !someone;
                        reconstruction_groups[r].traverse(function (object) {
                            object.visible = !someone;
                        });
                    }
                    render();
                }
            };

            document.getElementById('load_button').addEventListener('change', onReconstructionFileSelected, false);
            processUrlParams();


            function addDatGui(){
                var gui = new dat.GUI();

                f1 = gui.addFolder('Options');
                f1.add(options, 'pointSize', 0, 2)
                    .listen()
                    .onChange(setPointSize);
                f1.add(options, 'cameraSize', 0, 2)
                    .listen()
                    .onChange(setCameraSize);
                f1.add(options, 'imagePlaneSize', 1, 400)
                    .onChange(function(value) {
                        options.imagePlaneSize *= 1.5;
                        imagePlane.geometry = imagePlaneGeo(imagePlaneCameraOld.reconstruction, imagePlaneCameraOld.shot_id);
                        options.imagePlaneSize /= 1.5;
                        imagePlane.geometry = imagePlaneGeo(imagePlaneCamera.reconstruction, imagePlaneCamera.shot_id);
                        render();
                    });
                f1.add(options, 'animationSpeed', 0, 0.2)
                    .onChange(function(value) {
                        controls.animationSpeed = value;
                    });
                f1.add(options, 'resolution', [ '320', '640', 'original' ] );
                f1.add(options, 'showThumbnail')
                    .listen()
                    .onChange(setShowThumbnail);
                f1.add(options, 'drawGrid')
                    .listen()
                    .onChange(setDrawGrid);
                f1.add(options, 'showImagePlane')
                    .listen()
                    .onChange(setShowImagePlane);
                f1.add(options, 'drawGPS')
                    .listen()
                    .onChange(setDrawGPS);
                f1.open();


                var f3 = gui.addFolder('Reconstructions')
                f3.add(options, 'allNone');
                options.reconstruction_visibles = [];
                for (var r = 0; r < reconstructions.length; ++r) {
                    options.reconstruction_visibles[r] = true;
                    f3.add(options.reconstruction_visibles, r, true)
                        .onChange(
                            (function(rr) {
                                return function (value) {
                                    reconstruction_groups[rr].traverse(
                                        function (object) { object.visible = value; } );
                                    render();
                                }
                            })(r)
                        ).listen();
                }
                f3.close();

                gui.close();
            }

            function setPointSize(value) {
                options.pointSize = value;
                pointCloudMaterial.size = value;
                for (var i = 0; i < point_clouds.length; ++i) {
                    point_clouds[i].visible = (value > 0);
                }
                render();
            }

            function setCameraSize(value) {
                options.cameraSize = value;
                for (var r = 0; r < reconstructions.length; ++r) {
                    updateCameraLines(reconstructions[r]);
                }
                render();
            }

            function setShowThumbnail(value) {
                options.showThumbnail = value;
                $('#info').css('visibility', value ? 'visible' : 'hidden');
            }

            function setShowImagePlane(value) {
                options.showImagePlane = value;
                imagePlane.visible = value;
                if (movingMode === 'walk') {
                    imagePlaneOld.visible = value;
                } else {
                    imagePlaneOld.visible = false;
                }
                render();
            }

            function setDrawGrid(value) {
                options.drawGrid = value;
                grid_group.visible = value;
                render();
            }

            function setDrawGPS(value) {
                options.drawGPS = value;
                for (var i = 0; i < gps_lines.length; ++i) {
                    gps_lines[i].visible = value;
                }
                render();
            }

            function setMovingMode(mode) {
                if (mode != movingMode) {
                    movingMode = mode;
                    if (mode == 'orbit') {
                        resetWalkMode();
                        swapOptions();
                        controls.noRotate = false;
                        controls.noLookAround = false;
                        controls.noPan = false;
                        controls.noZoom = false;
                        controls.noKeys = false;
                        controls.animationPosition.z += 10;
                        controls.dollyOut(4);
                        imagePlane.material.depthWrite = true;
                        imagePlaneOld.material.depthWrite = true;
                        $('#navigation').hide();
                    } else if (mode == 'walk') {
                        swapOptions();
                        // controls.noRotate = true;
                        // controls.noLookAround = true;
                        // controls.noPan = true;
                        // controls.noZoom = true;
                        // controls.noKeys = true;
                        imagePlane.material.depthWrite = false;
                        imagePlaneOld.material.depthWrite = false;
                        $('#navigation').show();
                    }
                }
            }

            function resetWalkMode() {
                previousShot = undefined;
            }

            function swapOptions() {
                var tmpOptions = {
                    pointSize: savedOptions.pointSize,
                    cameraSize: savedOptions.cameraSize,
                    showThumbnail: savedOptions.showThumbnail,
                    showImagePlane: savedOptions.showImagePlane,
                    drawGrid: savedOptions.drawGrid,
                    drawGPS: savedOptions.drawGPS
                };

                savedOptions.pointSize = options.pointSize;
                savedOptions.cameraSize = options.cameraSize;
                savedOptions.showThumbnail = options.showThumbnail;
                savedOptions.showImagePlane = options.showImagePlane;
                savedOptions.drawGrid = options.drawGrid;
                savedOptions.drawGPS = options.drawGPS;

                setPointSize(tmpOptions.pointSize);
                setCameraSize(tmpOptions.cameraSize);
                setShowThumbnail(tmpOptions.showThumbnail);
                setShowImagePlane(tmpOptions.showImagePlane);
                setDrawGrid(tmpOptions.drawGrid);
                setDrawGPS(tmpOptions.drawGPS);
            }

            function imageURL(shot_id) {
                var url = urlParams.file;
                if (url === undefined) return;
                var slash = url.lastIndexOf('/');
                var imagePath = '/images' + options.resolution.replace('original', '')
                return url.substring(0, slash) + imagePath +'/' + shot_id;
            }

            function parseUrl() {
                var match,
                    pl     = /\+/g,  // Regex for replacing addition symbol with a space
                    search = /([^&=]+)=?([^&]*)/g,
                    decode = function (s) { return decodeURIComponent(s.replace(pl, " ")); },
                    hash  = window.location.hash.substring(1);

                urlParams = {};
                while (match = search.exec(hash))
                   urlParams[decode(match[1])] = decode(match[2]);
            }

            function processUrlParams() {
                parseUrl();

                if ('res' in urlParams) options.resolution = urlParams.res;

                if (urlParams.file !== undefined) {
                    loadReconstructionFromURL(urlParams.file);
                }
            }

            function onReconstructionFileSelected(evt) {
                let file = evt.target.files[0];
                console.log(file);

                $('#load_button').hide();
                $('#loading').show();

                var reader = new FileReader();
                reader.onload = function(e) {
                    data = JSON.parse(e.target.result);
                    setReconstructionData(data);
                };
                reader.readAsText(file);
            }

            function loadReconstructionFromURL(file_path) {
                $('#load_button').hide();
                $('#loading').show();
                jQuery.getJSON(file_path, setReconstructionData);
            }

            function setReconstructionData(data) {
                if ('cameras' in data) {
                    reconstructions = [data];
                } else {
                    reconstructions = data;
                }
                $('#loading').hide();
                init();
                animate();
            }

            function rotate(vector, angleaxis) {
                var v = new THREE.Vector3(vector[0], vector[1], vector[2]);
                var axis = new THREE.Vector3(angleaxis[0],
                                             angleaxis[1],
                                             angleaxis[2]);
                var angle = axis.length();
                axis.normalize();
                var matrix = new THREE.Matrix4().makeRotationAxis(axis, angle);
                v.applyMatrix4(matrix);
                return v;
            }

            function opticalCenter(shot) {
                var angleaxis = [-shot.rotation[0],
                                 -shot.rotation[1],
                                 -shot.rotation[2]];
                var Rt = rotate(shot.translation, angleaxis);
                Rt.negate();
                return Rt;
            }

            function viewingDirection(shot) {
                var angleaxis = [-shot.rotation[0],
                                 -shot.rotation[1],
                                 -shot.rotation[2]];
                return rotate([0,0,1], angleaxis);
            }

            function pixelToVertex(cam, shot, u, v, scale) {
                // Projection model:
                // xc = R * x + t
                // u = focal * xc / zc
                // v = focal * yc / zc
                var focal = cam.focal || 0.3;
                var zc = scale;
                var xc = u / focal * zc;
                var yc = v / focal * zc;

                var xct = [xc - shot.translation[0],
                           yc - shot.translation[1],
                           zc - shot.translation[2]];


                var angleaxis = [-shot.rotation[0],
                                 -shot.rotation[1],
                                 -shot.rotation[2]];

                return rotate(xct, angleaxis);
            }

            function initCameraLines(reconstruction) {
                var lines = []
                for (var shot_id in reconstruction.shots) {
                    if (reconstruction.shots.hasOwnProperty(shot_id)) {
                        var lineMaterial = new THREE.LineBasicMaterial({size: 0.1 })
                        var rid = reconstruction_id_of_shot(reconstructions, shot_id);
                        lineMaterial.color =  reconstruction_color(rid);
                        var linegeo = cameraLineGeo(reconstruction, shot_id);
                        var line = new THREE.Line(linegeo, lineMaterial, THREE.LinePieces);
                        line.reconstruction = reconstruction;
                        line.shot_id = shot_id;
                        lines.push(line);
                    }
                }
                return lines;
            }

            function updateCameraLines() {
                for (var i = 0; i < camera_lines.length; ++i) {
                    var linegeo = cameraLineGeo(camera_lines[i].reconstruction, camera_lines[i].shot_id);
                    camera_lines[i].geometry.vertices = linegeo.vertices;
                    camera_lines[i].geometry.verticesNeedUpdate = true;
                }
            }

            function cameraLineGeo(reconstruction, shot_id) {
                var shot = reconstruction.shots[shot_id];
                var cam = reconstruction.cameras[shot.camera];
                var ocenter = opticalCenter(shot);
                var dx = cam.width / 2.0 / Math.max(cam.width, cam.height);
                var dy = cam.height / 2.0 / Math.max(cam.width, cam.height);
                var top_left     = pixelToVertex(cam, shot, -dx, -dy, options.cameraSize);
                var top_right    = pixelToVertex(cam, shot,  dx, -dy, options.cameraSize);
                var bottom_right = pixelToVertex(cam, shot,  dx,  dy, options.cameraSize);
                var bottom_left  = pixelToVertex(cam, shot, -dx,  dy, options.cameraSize);
                var linegeo = new THREE.Geometry();
                linegeo.vertices.push(ocenter);
                linegeo.vertices.push(top_left);
                linegeo.vertices.push(ocenter);
                linegeo.vertices.push(top_right);
                linegeo.vertices.push(ocenter);
                linegeo.vertices.push(bottom_right);
                linegeo.vertices.push(ocenter);
                linegeo.vertices.push(bottom_left);
                linegeo.vertices.push(top_left);
                linegeo.vertices.push(top_right);
                linegeo.vertices.push(top_right);
                linegeo.vertices.push(bottom_right);
                // linegeo.vertices.push(bottom_right);
                // linegeo.vertices.push(bottom_left);
                linegeo.vertices.push(bottom_left);
                linegeo.vertices.push(top_left);
                return linegeo;
            }

            function imagePlaneGeo(reconstruction, shot_id) {
                var shot = reconstruction.shots[shot_id];
                var cam = reconstruction.cameras[shot.camera];

                if ('vertices' in shot && shot['vertices'].length > 0) {
                    var geometry = new THREE.Geometry();
                    for (var i = 0; i < shot['vertices'].length; ++i) {
                        geometry.vertices.push(
                            new THREE.Vector3(
                                shot['vertices'][i][0],
                                shot['vertices'][i][1],
                                shot['vertices'][i][2]
                            )
                        );
                    }
                    for (var i = 0; i < shot['faces'].length; ++i) {
                        var v0 = shot['faces'][i][0];
                        var v1 = shot['faces'][i][1];
                        var v2 = shot['faces'][i][2];

                        geometry.faces.push(new THREE.Face3(v0, v1, v2));
                    }
                    return geometry;
                } else {
                    if (cam.projection_type == "spherical" || cam.projection_type == "equirectangular") {
                        return imageSphereGeoFlat(cam, shot);
                    } else {
                        return imagePlaneGeoFlat(cam, shot);
                    }
                }
            }

            function imagePlaneGeoFlat(cam, shot) {
                var geometry = new THREE.Geometry();
                var dx = cam.width / 2.0 / Math.max(cam.width, cam.height);
                var dy = cam.height / 2.0 / Math.max(cam.width, cam.height);
                var top_left     = pixelToVertex(cam, shot, -dx, -dy, options.imagePlaneSize);
                var top_right    = pixelToVertex(cam, shot,  dx, -dy, options.imagePlaneSize);
                var bottom_right = pixelToVertex(cam, shot,  dx,  dy, options.imagePlaneSize);
                var bottom_left  = pixelToVertex(cam, shot, -dx,  dy, options.imagePlaneSize);

                geometry.vertices.push(
                    top_left,
                    bottom_left,
                    bottom_right,
                    top_right
                );
                geometry.faces.push(
                    new THREE.Face3(0, 1, 3),
                    new THREE.Face3(1, 2, 3)
                );
                return geometry;
            }

            function imageSphereGeoFlat(cam, shot) {
                geometry = new THREE.SphereGeometry(
                    options.imagePlaneSize,
                    20,
                    40
                );
                var center = pixelToVertex(cam, shot, 0, 0, 0);
                geometry.applyMatrix(new THREE.Matrix4().makeTranslation(center.x, center.y, center.z));
                return geometry;
            }

            function createImagePlaneMaterial(cam, shot, shot_id) {
                var imageTexture = THREE.ImageUtils.loadTexture(imageURL(shot_id));
                imageTexture.minFilter = THREE.LinearFilter;

                var material = new THREE.ShaderMaterial({
                    side: THREE.DoubleSide,
                    transparent: true,
                    depthWrite: true,
                    uniforms: {
                        projectorMat: {
                             type: 'm4',
                             value: projectorCameraMatrix(cam, shot)
                        },
                        projectorTex: {
                            type: 't',
                            value: imageTexture
                        },
                        opacity: {
                            type: 'f',
                            value: options.imagePlaneOpacity
                        },
                        focal: {
                            type: 'f',
                            value: cam.focal
                        },
                        focal_x: {
                            type: 'f',
                            value: cam.focal_x
                        },
                        focal_y: {
                            type: 'f',
                            value: cam.focal_y
                        },
                        c_x: {
                            type: 'f',
                            value: cam.c_x
                        },
                        c_y: {
                            type: 'f',
                            value: cam.c_y
                        },
                        k1: {
                            type: 'f',
                            value: cam.k1
                        },
                        k2: {
                            type: 'f',
                            value: cam.k2
                        },
                        k3: {
                            type: 'f',
                            value: cam.k3
                        },
                        k4: {
                            type: 'f',
                            value: cam.k4
                        },
                        scale_x: {
                            type: 'f',
                            value: Math.max(cam.width, cam.height) / cam.width
                        },
                        scale_y: {
                            type: 'f',
                            value: Math.max(cam.width, cam.height) / cam.height
                        }
                    },
                    vertexShader:   imageVertexShader(cam),
                    fragmentShader: imageFragmentShader(cam)
                });

                return material;
            }

            function imageVertexShader(cam) {
                return $('#vertexshader').text();
            }

            function imageFragmentShader(cam) {
                if (cam.projection_type == 'equirectangular' || cam.projection_type == 'spherical')
                    return $('#fragmentshader_equirectangular').text();
                else if (cam.projection_type == 'fisheye')
                    return $('#fragmentshader_fisheye').text();
                else if (cam.projection_type == 'fisheye_opencv')
                    return $('#fragmentshader_fisheye_opencv').text();
                else
                    return $('#fragmentshader').text();
            }

            function projectorCameraMatrix(cam, shot) {
                var angleaxis = shot.rotation;
                var axis = new THREE.Vector3(angleaxis[0],
                                             angleaxis[1],
                                             angleaxis[2]);
                var angle = axis.length();
                axis.normalize();
                var rotation = new THREE.Matrix4().makeRotationAxis(axis, angle);
                var t = shot.translation;
                var translation = new THREE.Vector3(t[0], t[1], t[2]);
                rotation.setPosition(translation);

                return rotation;
            }

            function reconstruction_color(id){
                var s = 1
                if(id !== 0){
                    s = Math.sin(id) * 10000;
                    s -= Math.floor(s);
                }
                var color = new THREE.Color();
                color.setHex( s * 0xffffff );
                return color;
            }

            function init() {
                raycaster = new THREE.Raycaster();
                raycaster.precision = 0.01;

                renderer = new THREE.WebGLRenderer();
                renderer.setSize(window.innerWidth, window.innerHeight);
                renderer.setClearColor( 0x202020, 0.0);
                renderer.sortObjects = false;

                container = document.getElementById( 'ThreeJS' );
                container.appendChild(renderer.domElement);

                camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.03, 10000);
                camera.position.x = 50;
                camera.position.y = 50;
                camera.position.z = 50;
                camera.up = new THREE.Vector3(0,0,1);

                controls = new THREE.OrbitControls(camera, renderer.domElement);
                controls.addEventListener('change', render);

                window.addEventListener('resize', onWindowResize, false);
                renderer.domElement.addEventListener('mousemove', onDocumentMouseMove, false);
                renderer.domElement.addEventListener('mousedown', onDocumentMouseDown, false);
                window.addEventListener( 'keydown', onKeyDown, false );

                scene_group = new THREE.Object3D();
                pointCloudMaterial = new THREE.PointCloudMaterial({
                      size: options.pointSize,
                      vertexColors: true,
                    });
                for (var r = 0; r < reconstructions.length; ++r) {
                    var reconstruction = reconstructions[r];
                    reconstruction_groups[r] = new THREE.Object3D();
                    var group = reconstruction_groups[r];

                    // Points.
                    var points = new THREE.Geometry();
                    for (var point_id in reconstruction.points) {
                        if (reconstruction.points.hasOwnProperty(point_id)) {
                            var p = reconstruction.points[point_id].coordinates;
                            var c = reconstruction.points[point_id].color;
                            var color = new THREE.Color();
                            color.setRGB(c[0] / 255., c[1] / 255., c[2] / 255.)
                            points.vertices.push(new THREE.Vector3(p[0], p[1], p[2]));
                            points.colors.push(color);
                        }
                    }
                    var point_cloud = new THREE.PointCloud(points, pointCloudMaterial);
                    point_clouds.push(point_cloud);
                    group.add(point_cloud);

                    // Cameras.
                    var lines = initCameraLines(reconstruction);
                    for (var i = 0; i < lines.length; ++i) {
                        group.add(lines[i]);
                        camera_lines.push(lines[i]);
                    }

                    // GPS positions
                    for (var shot_id in reconstruction.shots) {
                        if (reconstruction.shots.hasOwnProperty(shot_id)) {
                            var shot = reconstruction.shots[shot_id];
                            var ocenter = opticalCenter(shot);
                            var gps = shot.gps_position;

                            if (gps){
                                var linegeo = new THREE.Geometry();
                                linegeo.vertices.push(
                                    ocenter,
                                    new THREE.Vector3(gps[0], gps[1], gps[2])
                                );
                                var lineMaterial = new THREE.LineBasicMaterial({ color: 0xff00ff });
                                var line = new THREE.Line(linegeo, lineMaterial, THREE.LinePieces);
                                line.visible = options.drawGPS;
                                group.add(line);
                                gps_lines.push(line);
                            }
                        }
                    }

                    scene_group.add(group);
                }


                // Image plane
                imagePlaneCamera = camera_lines[0];
                var shot = imagePlaneCamera.reconstruction.shots[imagePlaneCamera.shot_id];
                var cam = imagePlaneCamera.reconstruction.cameras[shot.camera];

                imagePlane = new THREE.Mesh(imagePlaneGeo(imagePlaneCamera.reconstruction,
                                                          imagePlaneCamera.shot_id),
                                            createImagePlaneMaterial(cam, shot, imagePlaneCamera.shot_id));
                imagePlane.visible = options.showImagePlane;

                imagePlaneCameraOld = camera_lines[0];
                imagePlaneOld = new THREE.Mesh(imagePlaneGeo(imagePlaneCameraOld.reconstruction,
                                                             imagePlaneCameraOld.shot_id),
                                            createImagePlaneMaterial(cam, shot, imagePlaneCameraOld.shot_id));
                imagePlaneOld.visible = options.showImagePlane;

                scene_group.add(imagePlane);
                scene_group.add(imagePlaneOld);


                // Axis
                grid_group = new THREE.Object3D();
                var linegeo = new THREE.Geometry();
                linegeo.vertices = [
                    new THREE.Vector3(0, 0, 0),
                    new THREE.Vector3(1, 0, 0),
                    new THREE.Vector3(0, 0, 0),
                    new THREE.Vector3(0, 1, 0),
                    new THREE.Vector3(0, 0, 0),
                    new THREE.Vector3(0, 0, 1)
                ];
                linegeo.colors = [
                    new THREE.Color( 0xff0000 ),
                    new THREE.Color( 0xff0000 ),
                    new THREE.Color( 0x00ff00 ),
                    new THREE.Color( 0x00ff00 ),
                    new THREE.Color( 0x0000ff ),
                    new THREE.Color( 0x0000ff )
                ];
                var lineMaterial = new THREE.LineBasicMaterial({
                    color: 0xffffff,
                    vertexColors: THREE.VertexColors
                });
                var line = new THREE.Line(linegeo, lineMaterial, THREE.LinePieces);
                grid_group.add(line);

                // Ground grid
                {
                    var linegeo = new THREE.Geometry();
                    var N = 20;
                    var scale = 2;
                    for (var i = 0; i <= 2 * N; ++i) {
                        linegeo.vertices.push(
                            new THREE.Vector3(scale * (i - N), scale * (-N), 0),
                            new THREE.Vector3(scale * (i - N), scale * ( N), 0),
                            new THREE.Vector3(scale * (-N), scale * (i - N), 0),
                            new THREE.Vector3(scale * ( N), scale * (i - N), 0)
                        );
                    }
                    var lineMaterial = new THREE.LineBasicMaterial({color: 0x555555});
                    var line = new THREE.Line(linegeo, lineMaterial, THREE.LinePieces);
                    grid_group.add(line);
                }
                scene_group.add(grid_group);

                scene = new THREE.Scene();
                scene.add(scene_group);

                addDatGui();

                setShowThumbnail(true);
                if ('img' in urlParams) {
                    for (var i = 0; i < camera_lines.length; ++i) {
                        if (camera_lines[i].shot_id.indexOf(urlParams.img) > -1) {
                            var initialCamera = camera_lines[i];
                            setMovingMode('walk');
                            setImagePlaneCamera(initialCamera);
                            navigateToShot(initialCamera);
                            break;
                        }
                    }
                }

                if (camera_lines.length < 50) {
                    preloadAllImages();
                }

                render();
            }

            function onWindowResize() {
                camera.aspect = window.innerWidth / window.innerHeight;
                camera.updateProjectionMatrix();
                renderer.setSize(window.innerWidth, window.innerHeight);
                render();
            }

            function onDocumentMouseMove(event) {
                event.preventDefault();
                mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
                mouse.y = - (event.clientY / window.innerHeight) * 2 + 1;
                render();
            }

            function reconstruction_of_shot(reconstructions, shot_id) {
                for (var r = 0; r < reconstructions.length; ++r) {
                    if (shot_id in reconstructions[r]['shots']) {
                        return reconstructions[r];
                    }
                }
                return undefined;
            }

            function reconstruction_id_of_shot(reconstructions, shot_id) {
                for (var r = 0; r < reconstructions.length; ++r) {
                    if (shot_id in reconstructions[r]['shots']) {
                        return r;
                    }
                }
                return undefined;
            }

            function setSelectedCamera(cameraObject) {
                var r = cameraObject.reconstruction;
                var shot_id = cameraObject.shot_id;
                var shot = r['shots'][shot_id];
                var image_url = imageURL(shot_id);
                var rid = reconstruction_id_of_shot(reconstructions, shot_id);
                if (selectedCamera !== undefined) {
                    selectedCamera.material.linewidth = 1;
                    selectedCamera.material.color = reconstruction_color(rid);
                }
                selectedCamera = cameraObject;
                selectedCamera.material.linewidth = 5;
                selectedCamera.material.color = options.selectedCameraColor;
                var image_tag = document.getElementById('image');
                image_tag.src = image_url;
                var text = document.getElementById('text');
                text.innerHTML = shot_id;
                var reconstruction = document.getElementById('reconstruction');
                reconstruction.innerHTML = rid;
            }

            function setImagePlaneCamera(cameraObject) {
                var r = cameraObject.reconstruction;
                var shot_id = cameraObject.shot_id;
                var shot = r['shots'][shot_id];
                var cam = r['cameras'][shot['camera']];

                if (previousShot !== cameraObject.shot_id) {
                    previousShot = cameraObject.shot_id
                    var image_url = imageURL(shot_id);
                    if (selectedCamera !== cameraObject) {
                        setSelectedCamera(cameraObject);
                    }

                    if (imagePlaneCamera !== undefined) {
                        if (imagePlaneCameraOld === undefined || imagePlaneCamera.shot_id !== cameraObject.shot_id) {
                            imagePlaneCameraOld = imagePlaneCamera;
                            imagePlaneOld.material.uniforms.projectorTex.value = imagePlane.material.uniforms.projectorTex.value;
                            imagePlaneOld.material.uniforms.projectorMat.value = imagePlane.material.uniforms.projectorMat.value;
                            imagePlane.material.uniforms.focal.value = imagePlane.material.uniforms.focal.value;
                            imagePlane.material.uniforms.focal_x.value = imagePlane.material.uniforms.focal_x.value;
                            imagePlane.material.uniforms.focal_y.value = imagePlane.material.uniforms.focal_y.value;
                            imagePlane.material.uniforms.c_x.value = imagePlane.material.uniforms.c_x.value;
                            imagePlane.material.uniforms.c_y.value = imagePlane.material.uniforms.c_y.value;
                            imagePlane.material.uniforms.k1.value = imagePlane.material.uniforms.k1.value;
                            imagePlane.material.uniforms.k2.value = imagePlane.material.uniforms.k2.value;
                            imagePlane.material.uniforms.k3.value = imagePlane.material.uniforms.k3.value;
                            imagePlane.material.uniforms.k4.value = imagePlane.material.uniforms.k4.value;
                            imagePlane.material.uniforms.scale_x.value = imagePlane.material.uniforms.scale_x.value;
                            imagePlane.material.uniforms.scale_y.value = imagePlane.material.uniforms.scale_y.value;
                            imagePlaneOld.material.vertexShader = imagePlane.material.vertexShader;
                            imagePlaneOld.material.fragmentShader = imagePlane.material.fragmentShader;
                            imagePlaneOld.material.needsUpdate = true;

                            imagePlaneOld.geometry.dispose();
                            imagePlaneOld.geometry = imagePlaneGeo(imagePlaneCameraOld.reconstruction, imagePlaneCameraOld.shot_id);
                        }

                        if (movingMode === 'walk') {
                            options.imagePlaneOpacity = 1;
                        }
                    }

                    imagePlaneCamera = cameraObject;
                    imagePlane.material.dispose();
                    imagePlane.geometry.dispose();
                    imagePlane.material = createImagePlaneMaterial(cam, shot, shot_id);
                    imagePlane.geometry = imagePlaneGeo(r, shot_id);
                }
            }

            function setImagePlaneCameraList(cameraObject, id) {
                var r = cameraObject.reconstruction;
                var shot_id = cameraObject.shot_id;
                var shot = r['shots'][shot_id];
                var cam = r['cameras'][shot['camera']];
                var image_url = imageURL(shot_id);

                imagePlaneCameras[id] = cameraObject;
                imageMaterials[id].map = THREE.ImageUtils.loadTexture(image_url, null, render);
                imageMaterials[id].map.minFilter = THREE.LinearFilter;
                imagePlanes[id].geometry = imagePlaneGeo(r, shot_id);
                imagePlanes[id].visible = true;
            }

            function onDocumentMouseDown(event) {
                window.focus();
                if (hoverCamera !== undefined) {
                    if (movingMode !== 'walk') {
                        if (selectedCamera !== hoverCamera) {
                            setSelectedCamera(hoverCamera);
                            setImagePlaneCamera(hoverCamera);
                        } else {
                            setMovingMode('walk');
                            setImagePlaneCamera(selectedCamera);
                            navigateToShot(selectedCamera);
                        }
                    }
                    render();
                }
            }

            function navigateToShot(camera) {
                var reconstruction = camera.reconstruction;
                var shot = reconstruction['shots'][camera.shot_id];
                var cam = reconstruction['cameras'][shot['camera']];
                controls.goto_shot(cam, shot);
            }

            function hideImagePlanesList(){
                for (var i =0; i < num_preview_plane; ++i) {
                    imagePlanes[i].visible = false;
                }
            }

            function angleBetweenVector2(x1, y1, x2, y2) {
                var a = Math.atan2(y2, x2) - Math.atan2(y1, x1);
                if (a > Math.PI) return a - 2 * Math.PI;
                else if (a < -Math.PI) return a + 2 * Math.PI;
                else return a;
            }

            function computeValidMoves() {
                var currentPosition = controls.animationPosition;
                var currentTarget = controls.animationTarget;
                var currentDir = currentTarget.clone().sub(currentPosition);
                var turnAngle = undefined;

                var wantedMotionDirs = {
                    STEP_LEFT: new THREE.Vector3(-currentDir.y, currentDir.x, 0),
                    STEP_RIGHT: new THREE.Vector3(currentDir.y, -currentDir.x, 0),
                    STEP_FORWARD: new THREE.Vector3(currentDir.x, currentDir.y, 0),
                    STEP_BACKWARD: new THREE.Vector3(-currentDir.x, -currentDir.y, 0),
                    TURN_LEFT: new THREE.Vector3(0, 0, 0),
                    TURN_RIGHT: new THREE.Vector3(0, 0, 0),
                    TURN_U: new THREE.Vector3(0, 0, 0)
                }

                var wantedDirs = {
                    STEP_LEFT: new THREE.Vector3(currentDir.x, currentDir.y, 0),
                    STEP_RIGHT: new THREE.Vector3(currentDir.x, currentDir.y, 0),
                    STEP_FORWARD: new THREE.Vector3(currentDir.x, currentDir.y, 0),
                    STEP_BACKWARD: new THREE.Vector3(currentDir.x, currentDir.y, 0),
                    TURN_LEFT: new THREE.Vector3(-currentDir.y, currentDir.x, 0),
                    TURN_RIGHT: new THREE.Vector3(currentDir.y, -currentDir.x, 0),
                    TURN_U: new THREE.Vector3(-currentDir.x, -currentDir.y, 0)
                }

                var min_d = {};
                var closest_line = {};
                var turn_threshold;
                for (var k in wantedMotionDirs) {
                    if (wantedMotionDirs.hasOwnProperty(k)) {
                        min_d[k] = 999999999999;
                        closest_line[k] = undefined;
                    }
                }

                for (var i = 0; i < camera_lines.length; ++i) {
                    var line = camera_lines[i];
                    var r = line.reconstruction;
                    var shot_id = line.shot_id;
                    var shot = r['shots'][shot_id];
                    var cam = r['cameras'][shot['camera']];
                    var oc = opticalCenter(shot);
                    var dir = (cam.projection_type == 'spherical' || cam.projection_type == 'equirectangular') ? currentDir : viewingDirection(shot);
                    var motion = oc.clone().sub(currentPosition);
                    var d = currentPosition.distanceTo(oc);
                    var rid = reconstruction_id_of_shot(reconstructions, shot_id);
                    var visible = options.reconstruction_visibles[rid];
                    if (!visible) continue;

                    for (var k in wantedMotionDirs) {
                        if (wantedMotionDirs.hasOwnProperty(k)) {
                            var turn = angleBetweenVector2(wantedDirs[k].x, wantedDirs[k].y, dir.x, dir.y);
                            var driftAB = angleBetweenVector2(wantedMotionDirs[k].x, wantedMotionDirs[k].y, motion.x, motion.y);
                            var driftBA = driftAB - turn;
                            var drift = Math.max(driftAB, driftBA);
                            if (k.lastIndexOf('STEP', 0) === 0) {
                                turn_threshold = 0.5
                                if (Math.abs(turn) < turn_threshold && Math.abs(drift) < 0.5 && d > 0.01 && d < 20) {
                                    if (d < min_d[k]) {
                                        min_d[k] = d;
                                        closest_line[k] = line;
                                    }
                                }
                            } else if (k.lastIndexOf('TURN', 0) === 0) {
                                if (Math.abs(turn) < 0.7 && d < 15) {
                                    if (d < min_d[k]) {
                                        min_d[k] = d;
                                        closest_line[k] = line;
                                    }
                                }
                            }
                        }
                    }
                }
                return closest_line;
            }

            function walkOneStep(motion_type) {
                var line = validMoves[motion_type];
                if (line !== undefined) {
                    setImagePlaneCamera(line);
                    navigateToShot(line);
                }
            }

            function onKeyDown(event) {
                if (movingMode == 'walk') {
                    var validKey = true;

                    switch (event.keyCode) {
                        case 37: // left arrow
                            if (event.shiftKey) {
                                walkOneStep('TURN_LEFT');
                            } else {
                                walkOneStep('STEP_LEFT');
                            }
                            break;
                        case 38: // up arrow
                            walkOneStep('STEP_FORWARD');
                            break;
                        case 39: // right arrow
                            if (event.shiftKey) {
                                walkOneStep('TURN_RIGHT');
                            } else {
                                walkOneStep('STEP_RIGHT');
                            }
                            break;
                        case 40: // down arrow
                            if (event.shiftKey) {
                                walkOneStep('TURN_U');
                            } else {
                                walkOneStep('STEP_BACKWARD');
                            }
                            break;
                        case 27: // ESC
                            setMovingMode('orbit');
                            break;
                        default:
                            validKey = false;
                            break;
                    }

                    if (validKey) {
                        event.preventDefault();
                    }
                }
            }

            function preloadAllImages() {
                for (var i = 0; i < camera_lines.length; ++i) {
                    var shot_id = camera_lines[i].shot_id;
                    var image_url = imageURL(shot_id);
                    var temp_img = new Image();
                    temp_img.src = image_url;
                }
            }

            function preloadValidMoves() {
                for (var k in validMoves) {
                    if (validMoves.hasOwnProperty(k)) {
                        var line = validMoves[k];
                        if (line !== undefined) {
                            var shot_id = line.shot_id;
                            var image_url = imageURL(shot_id);
                            var temp_img = new Image();
                            temp_img.src = image_url;
                        }
                    }
                }
            }

            function updateValidMovesWidget() {
                $('#nav-left').css('visibility',
                    (validMoves.STEP_LEFT === undefined) ? 'hidden':'visible');
                $('#nav-right').css('visibility',
                    (validMoves.STEP_RIGHT === undefined) ? 'hidden':'visible');
                $('#nav-forward').css('visibility',
                    (validMoves.STEP_FORWARD === undefined) ? 'hidden':'visible');
                $('#nav-backward').css('visibility',
                    (validMoves.STEP_BACKWARD === undefined) ? 'hidden':'visible');
                $('#nav-turn-left').css('visibility',
                    (validMoves.TURN_LEFT === undefined) ? 'hidden':'visible');
                $('#nav-turn-right').css('visibility',
                    (validMoves.TURN_RIGHT === undefined) ? 'hidden':'visible');
                $('#nav-u-turn').css('visibility',
                    (validMoves.TURN_U === undefined) ? 'hidden':'visible');
            }

            function animate() {
                requestAnimationFrame(animate);
                imagePlane.material.uniforms.opacity.value = 1 - options.imagePlaneOpacity;
                if (imagePlaneOld !== undefined) {
                    imagePlaneOld.material.uniforms.opacity.value = 1;
                }
                options.imagePlaneOpacity *= 1 - options.animationSpeed;

                controls.update();
            }

            function render() {
                validMoves = computeValidMoves();
                updateValidMovesWidget();
                preloadValidMoves();

                // Handle camera selection.
                if (hoverCamera !== undefined && hoverCamera !== selectedCamera) {
                    hoverCamera.material.linewidth = 1;
                    var rid = reconstruction_id_of_shot(reconstructions, hoverCamera.shot_id);
                    hoverCamera.material.color = reconstruction_color(rid);
                }
                var vector = new THREE.Vector3(mouse.x, mouse.y, 1).unproject(camera);
                raycaster.set(camera.position, vector.sub(camera.position).normalize());
                var intersects = raycaster.intersectObjects(camera_lines, true);
                hoverCamera = undefined;
                for (var i = 0; i < intersects.length; ++i) {
                    if (intersects[i].distance > 1.5 * options.cameraSize
                        && intersects[i].object.visible) {
                        hoverCamera = intersects[i].object;
                        if (hoverCamera !== selectedCamera) {
                            hoverCamera.material.linewidth = 2;
                            hoverCamera.material.color = options.hoverCameraColor;
                        }
                        break;
                    }
                }

                // Render.
                renderer.render(scene, camera);
            }
        </script>

    </body>
</html>
